package edu.siu.math.egut.local;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Set;
import java.util.Map.Entry;


import edu.siu.math.egut.io.Constants;
import edu.siu.math.egut.io.LiE;
import edu.siu.math.egut.io.OutputPref;
import edu.siu.math.egut.local.Inequality.Symbol;
import edu.siu.math.egut.main.OutputStyle;
import edu.siu.math.egut.main.SectionIntegralCalculator;
import edu.siu.math.egut.util.Algorithms;
import edu.siu.math.egut.util.BadSubstitutionException;
import edu.siu.math.egut.util.Group;
import edu.siu.math.egut.util.Monomial;
import edu.siu.math.egut.util.NotARootException;
import edu.siu.math.egut.util.Parabolic;
import edu.siu.math.egut.util.ParabolicReadException;
import edu.siu.math.egut.util.Polynomial;
import edu.siu.math.egut.util.PolynomialReadException;
import edu.siu.math.egut.util.Sunipotent;
import edu.siu.math.egut.util.Unipotent;
import edu.siu.math.egut.util.UnipotentReadException;
import edu.siu.math.egut.util.WeylWord;
import edu.siu.math.egut.util.WeylWordReadException;
import edu.siu.math.egut.util.Unipotent.PrefixRootPair;

public class SectionIntegral implements Serializable{
    
    public static class UnipotentPrefixPair {
	private final Unipotent u;
	private final String prefix;
	public Unipotent getUnipotent() {
	    return u;
	}
	public String getPrefix() {
	    return prefix;
	}
	public UnipotentPrefixPair(Unipotent u, String prefix) {
	    super();
	    this.u = u;
	    this.prefix = prefix;
	}

    }



    /**
     * 
     */
    private static final long serialVersionUID = -4172558993735135568L;
    private static final String DIVIDER = "\\\\\\\\";
    public static final String PLACE_HOLDER_STRING = "PLACE_HOLDER";
    private final Domain d;
    private final Group g;
    private WeylWord w;
    private final Parabolic p;
    private Unipotent u;
    private Polynomial q;
    private Polynomial r;
    private Polynomial j;


    
    public SectionIntegral(Domain d, Group g, WeylWord w, Parabolic p,
	    Unipotent u, Polynomial q, Polynomial r, Polynomial j) {
	super();
	this.d = d;
	this.g = g;
	this.w = w;
	this.p = p;
	this.u = u;
	this.q = q;
	this.r = r;
	this.j = j;
    }



    public Unipotent getU() {
        return u;
    }



    public void setU(Unipotent u) {
        this.u = u;
    }



    public Domain getD() {
        return d;
    }



    public Group getG() {
        return g;
    }



    public WeylWord getW() {
        return w;
    }



    public Parabolic getP() {
        return p;
    }



    public Polynomial getQ() {
        return q;
    }



    public Polynomial getR() {
        return r;
    }



    public Polynomial getJ() {
        return j;
    }

    
   public static SectionIntegral load (String filename){
        
        
        try{
          //use buffering
          InputStream file = new FileInputStream( filename );
          InputStream buffer = new BufferedInputStream( file );
          ObjectInput input = new ObjectInputStream ( buffer );
          try{
            //deserialize the List
            SectionIntegral answer =  (SectionIntegral)input.readObject();
            /* ISSUE:  answer will contain Monomials, Polynomials
             * etc., which have not been entered into the 
             * stores for these objects.
             * 
             */
            return  answer.update( );
           
          }
          finally{
            input.close();
          }
        }
        catch(ClassNotFoundException ex){
          System.err.println(ex);
        }
        catch(IOException ex){
            System.err.println(ex);
        }
    

        
        return null;
    
    
}



public SectionIntegral update() {
    return new SectionIntegral(
	    d.update(), 
	    Group.getGroup(g.getRank()), 
	    w,
	    p, 
	    u.update(), 
	    Polynomial.update(q),
	    Polynomial.update(r), 
	    Polynomial.update(j));
    // the purpose of this method is to ensure that any Polynomials, etc., which are deserialized 
    // are also added to the store.  ergo, if a store for Weyl elements of parabolics is implemented,
    // then one will need update methods for those classes as well.
}





//TODO test this method.
    public static SectionIntegral read(String string) throws DomainReadException, InequalityReadException, ParabolicReadException, UnipotentReadException, NotARootException, WeylWordReadException, SectionIntegralReadException
	     {
    
    String[] parts = string.split(DIVIDER);
    if(parts.length!=8){
	System.out.println("parts length = "+parts.length);
	for(int i = 0 ; i < parts.length; i++)
	    System.out.println("parts["+i+"]="+parts[i]);
	throw new SectionIntegralReadException();
    }
    
	try {
	    // domain
	    System.out.println("reading domain "+ parts[0]);
	    Domain d = Domain.read(parts[0]);
	    System.out.println("domain = " + d.toString());
	    // group
	    System.out.println("reading group "+ parts[1]);
	    Group g = Group.getGroup(parts[1]);
	    System.out.println("g="+g);
	    
	    // Polynomial
	    Polynomial j = Polynomial.read(parts[2]);
	    // parabolic
	    System.out.println("Reading parabolic "+parts[3]);
	    Parabolic p = Parabolic.read(g, parts[3]);
	    System.out.println("p="+p);
	    // Polynomial
	    Polynomial q = Polynomial.read(parts[4]);
	    // Polynomial
	    Polynomial r = Polynomial.read(parts[5]);
	    // Unipotent
	    System.out.println("Reading unipotent "+parts[6]);
	    UnipotentPrefixPair pair = Unipotent.read(parts[6]);
	    Unipotent u = pair.getUnipotent();
	    String pre = pair.getPrefix();
	    System.out.println("u="+u);
	    // WeylWord
	    System.out.println("Reading Weyl word "+parts[7]);
	    WeylWord w = WeylWord.read(parts[7].trim(), g);
	    System.out.println("w="+w);
	    
	    
	    return new SectionIntegral(d,g,w,p,u,q,r,j);
	}    catch( PolynomialReadException e){
	
    } catch (WeylWordReadException e) {
	    //generated catch block
	    e.printStackTrace();
	    throw e;
	} catch (NotARootException e) {
	//generated catch block
	e.printStackTrace();
	    throw e;
    } catch (UnipotentReadException e) {
	//generated catch block
	e.printStackTrace();
	    throw e;
    } catch (ParabolicReadException e) {
	//generated catch block
	e.printStackTrace();
	    throw e;
    } catch (InequalityReadException e) {
	//generated catch block
	e.printStackTrace();
	    throw e;
    } catch (DomainReadException e) {
	
	e.printStackTrace();
	    
	throw e;
    }

  return null;


}

    public String toString() {
	return toString(OutputStyle.SCREEN);
    }


    public String toString(OutputStyle sty) {
	StringBuilder sb = new StringBuilder();
	
	switch (sty){
	case SCREEN:
	    sb.append(u.toString(true));
	    sb.append("\npsi("+q.toString()+")");
	    if( !r.equals(Polynomial.create(1)))
		    sb.append("\tchi("+r.toString() 
		    + ")");
	    if( !j.equals(Polynomial.create(1)))
		    sb.append( "\t|" + j.toString()+ "|");
	    sb.append(d.constraintsToString(sty));
	    break;
	case TEX:
	    String initialString ="\\begin{aligned}" +
		"\\int_Df_{\\chi}(wu)";
	    sb.append(initialString);
	    int width = 10;
	    if (!q.equals(Polynomial.create(0))){
		String qString ="\\psi\\left(" + q.toString(sty, Constants.PAGE_WIDTH - 5) + "\\right)";
		if(width + qString.length() > Constants.PAGE_WIDTH){
		    sb.append("\\\\\n");
		    width = 0;
		}
		sb.append(qString);
	    }
	    if (!r.equals(Polynomial.create(1))){
		String rString = "\\chi\\left(" + r.toString(sty, Constants.PAGE_WIDTH - 5) + "\\right)";
		if(width + rString.length() > Constants.PAGE_WIDTH){
		    sb.append("\\\\\n");
		    width = 0;
		}
		sb.append(rString);
	    }
	    if (!j.equals(Polynomial.create(1))){
		String jString = "\\left|" + j.toString(sty, Constants.PAGE_WIDTH - 5) + "\\right|";
		if(width + jString.length() > Constants.PAGE_WIDTH){
		    sb.append("\\\\\n");
		    width = 0;
		}
		sb.append(jString);
	    }
	    sb.append("\\,du,\\\\\n");
	    sb.append("u=" + u.toString(OutputStyle.TEX) + ",\\\\\n");
	    sb.append("D=" + d.toString(OutputStyle.TEX) + ",\\\\\n");
	    sb.append("w=" + w.toString(OutputStyle.TEX) + "\\end{aligned}\n");
	    
	}
	
	return sb.toString();
    }



    /**
     * The if si represents a section integral, then si.iwasawa0() represents the integral
     * obtained by restricting the argument of the rightmost sunipotent which appears inside
     * of the section in si to the ring of integers of the ambient non-Archimedean local 
     * field.  The section is then invariant by this sunipotent, so si.iwasawa0() has 
     * one fewer sunipotent appearing in the section.  
     * @return new section integral with restricted domain
     */
    public SectionIntegral iwasawa0() {
	       if(!u.getLast().getArgument().isASignedMonomial() ){
		   System.out.println("Argument of last term is not a signed monomial.  Iwasawa blocked.");
		   return this;
	       }
	      
	
	Unipotent v = Unipotent.copyOf(u);
	v.remove(v.getNumberOfTerms() - 1);
	/*
	 * Suppose that the argument of the last Sunipotent is a single variable x, which 
	 * does not appear anywhere else in the integrand, or in the bounds for the domain 
	 * of integration.  Then when we constrain this variable to be in the ring of
	 * integers we can just do the integral in this variable, getting one.  In other 
	 * words, in that situation, we can just drop the variable.
	 */

	Polynomial poly = u.getLast().getArgument();
	if (poly.isAVariable()) {

	    String x = poly.getVar();
	    if(d.doesNotConstrain(x)
		    ){
		if(v.isIndependentOf(x)&&
			    q.isIndependentOf(x)&&
			    r.isIndependentOf(x)&&
			    j.isIndependentOf(x)){
			    ArrayList<String> newVars = d.getVariables();
			    newVars.remove(x);
			    Domain d1 = new Domain(newVars, d.getBounds());

		return new SectionIntegral(d1,g, w, p, v, q, r, j); 
		    }}

	}	    

	/*If the argument of the last Sunipotent is not a variable, or if it appears elsewhere in 
	 * the integral, then it's not clear what will happen next, so the thing to 
	 * do is just drop the last Sunipotent from the integral, and then add the new bound
	 * to the domain.
	 * 
	 */
	return new SectionIntegral(d.addBound(
		Polynomial.create(1),
		Inequality.Symbol.GREATERTHANOREQUALTO, 
		u.getLast().getArgument()), 
		g, w, p, v, q, r, j);
    }
    /*FIXME  there seems to be some confusion as to how the inequalities
     * ought to be read.  I really need to make my mind up about that.
     * Does u < 1 mean |u| <1 or ord(u) < ord(1)... because the two
     * are opposite!
     * 
     */



 


    /**
     * The if si represents a section integral, then si.iwasawa1() represents
     * the integral obtained by restricting the argument of the rightmost
     * sunipotent which appears inside of the section in si to the complement
     * of the ring of integers in the ambient non-Archimedean local 
     * field.  The iwasawa decomposition for this rightmost sunipotent is 
     * plugged in and conjugated to the right.  So, si.iwasawa0() has 
     * one fewer sunipotent appearing in the section.  
     * @return new section integral with restricted domain
     * @throws BadSubstitutionException 
     * @throws UncomputableJacobianException 
     * @throws NotARootException 
     * @throws IOException 
     */
   public SectionIntegral iwasawa1() throws BadSubstitutionException, UncomputableJacobianException, NotARootException, IOException {
       
       
       if(!u.getLast().getArgument().isASignedMonomial() ){
	   System.out.println("Argument of last term is not a signed monomial.  Iwasawa blocked.");
	   return this;
       }
       Sunipotent suni = u.getLast();
       Polynomial arg = suni.getArgument();
       Unipotent v = Unipotent.copyOf(u);
       Polynomial j1 = j;
       Polynomial q1 = q;
       Polynomial r1 = r.times( character(suni) );
       v.remove(v.getNumberOfTerms()-1);
       
       SectionIntegral answer = new SectionIntegral(d.removeBound(
		Polynomial.create(1),
		Inequality.Symbol.GREATERTHANOREQUALTO, 
		u.getLast().getArgument()).
		addBound(
		u.getLast().getArgument(),
		Inequality.Symbol.GREATERTHAN, 
		Polynomial.create(1)), g, w, p, v, q1, r1, j1);

       
       for(int i = 0; i < v.getNumberOfTerms(); i++){
	   if(v.stringOf(v.get(i)) != null){
	       // then we get x_r( y t^{-<r, a^vee>}) and make the substitution 
	       // y -> y t^{<r, a^vee>}
	       ;
	       
	       String x = v.stringOf(v.get(i));
	       Polynomial sub  = arg.raiseTo((2*u.getGroup().weylInvtInnProd(v.get(i).getRoot(), suni.getRoot()))
		       /u.getGroup().weylInvtInnProd(suni.getRoot(), suni.getRoot()))
		       .times(x);
	       answer.replaceWithAnException(x,sub,i);
	       
	       
	       
	   }else{
	       // then we get x_r( p * t^{-<r, a^vee>})
	       // (and don't make any change of variables in response).
	       Sunipotent s1=  Sunipotent.create(v.get(i).getRoot(),
		       v.getArg(i).times(
			       arg.raiseTo(
				       (-2*u.getGroup().weylInvtInnProd(v.get(i).getRoot(), suni.getRoot()))
				       /u.getGroup().weylInvtInnProd(suni.getRoot(), suni.getRoot())))
		       );
	       
	       answer.getU().replace(i, s1);
	       
			       
	       
	   }

       }
	   answer.getU().rightMultiply(Sunipotent.create(g.negative(suni.getRoot()), suni.getArgument()));
	   answer.getU().pushFarLeftAndRemove(v.getNumberOfTerms()-1);
	   answer.lReduce();
	   answer.cleanUp();
	   answer.makeChangesOfVariable();

	return answer;
    }


/* Consider a Sunipotent x_a(r).  Assume that this Sunipotent 
 * appears rightmost in an unramified section, and that |r| >1.
 * Plugging in the Iwasawa decomposition one has 
 * 
 * f( w ... x_a(r) ) = f(w ... a^vee( r ) x_{-a}(r) )
 *                   = f( w a^\vee(r) w^{-1} w ... x_{-a}(r) ).  
 * Now assume that f is in a representation induced from 
 * \chi( \delta_P ) for some maximal parabolic P and character
 * \chi.  Then we get a factor of \chi(\delta_p( w a^\vee w^{-1} ) ) appearing.
 * 
 * The purpose here is to compute \delta_P( w a^\vee w^{-1} ).
 * 
 *  This is quite easy to do with LiE, and, for now at least, that seems
 *  the best way to do it.
 * 
 */
    private Polynomial character(Sunipotent suni) throws IOException {
	
	LiE lie = new LiE();
	
	// formulae:
	// delta_P = sum( unipRad( <parabolic specifier for P> , g ))
	lie.exec("deltaP = sum(unipRad("+p.getSpecifier() 
		+"," + g.toString() + "))", OutputPref.HIDE);
	
	// delta_P( w a^vee w^{-1} ) = a^n, where 
	//   n= delta_P*W_rt_action(w,g)*Cartan(g)*[a^vee],
	// where [a^vee] is the representation for a^vee as an 
	// integral vector in terms of the basis of simple coroots.
	int n = Integer.parseInt(lie.exec("deltaP*W_rt_action("+w.toString(OutputStyle.LIE)
		+ "," + g.toString() + ")*Cartan("+ g.toString()
		+ ")*" + corootString( suni.getRoot(), g), OutputPref.HIDE));
	return suni.getArgument().raiseTo(n);
	
    }



    private String corootString(int[] root, Group g2) {
	if(g2 == Group.G2){
	    if( root[0]%3 ==0)
		return("[" + (root[0]/3) + "," + root[1] + "]");
	    else 
		return("["+ root[0] + "," + (3*root[1])+"]");
	}
	if( g2 == Group.F4){
	    if( root[0]%2 == 0 && root[1]%2 == 0 )
		return "[" + (root[0]/2) + "," + (root[1]/2) +","
		+ root[2] + "," + root[3] + "]";
	    else 
		return  "[" + (root[0]) + "," + (root[1]) +","
		+ (2*root[2]) + "," + (2*root[3]) + "]";
	}
 	    
	return edu.siu.math.egut.io.Algorithms.tupleToString(root);
    }



    private void replaceWithAnException(String x, Polynomial sub, int i) throws BadSubstitutionException, UncomputableJacobianException {
	       
//	System.out.println("BEGIN replace with an exception");
//	System.out.println(x+"-->" + sub + "IN J");
//	System.out.println(j.toString());
		j=j.replace(x,sub ).times(jacobian(x, sub));
//		System.out.println(j.toString());
	       
//		System.out.println(x+"-->" + sub + "IN Q");
		System.out.println(q.toString());
	       q=q.replace(x,sub ); 
	       System.out.println(q.toString());

//	       System.out.println(x+"-->" + sub + "IN R");
//		System.out.println(r.toString());
	       r=r.replace(x,sub );
//		System.out.println(r.toString());
	       
//		System.out.println(x+"-->" + sub + "IN D");
		//System.out.println(d.toString());
	       d.replace(x, sub);
		//System.out.println(d.toString());
	       
	       
//		System.out.println(x+"-->" + sub + "IN U (except at "+i+")");
	       //u.listAssociations();
//	       System.out.println(u.toString());
	       u.replaceWithAnException(x,sub,i );
	       //u.listAssociations();
//	       System.out.println(u.toString());	       
//	       System.out.println(x+"-->" + sub + " FINISHED");

	
    }



    /**
     * Save the SectionIntegral Object
     * @param filename
     * @param si
     */
    public static void save(final String filename, final SectionIntegral si) {
        try{
            //use buffering
            OutputStream file = new FileOutputStream( filename
        	    +(
        		    (filename.endsWith(".ser")?"":".ser"
        			    )));
            OutputStream buffer = new BufferedOutputStream( file );
            ObjectOutput output = new ObjectOutputStream( buffer );
            try{
              output.writeObject(si);
            }
            finally{
              output.close();
            }
          }  
          catch(IOException ex){
            System.err.println(ex);
          }
	
    }



    public void move(int from, int to) {
	u.move(from, to);
	
    }



    public void cleanUp() {
	u.cleanUp();
	
	
    }



    public void makeChangesOfVariable() throws BadSubstitutionException {
	
	
	
	for (int i = 0; i < u.getNumberOfTerms(); i++) {
	    
	    /* get the String associated to Sunipotent i, 
	     * (If Sunipotent i has no String, then stringOf() returns "null."
	     */
	    String s1 = u.stringOf(u.get(i));
	    
	    
	    if (s1 != null) {// i.e., if our Sunipotent is indeed associated to a variable...
			
		/* there are two cases that can be handled. Write arg = current argument of our Sunipotent.
		 * 1. arg = s1 + q, where q is independent of s1.  In this case, the substitution 
		 * 	s1 -> s1 - q is indicated.
		 * 2. arg = -s1+q.  In this case, the substitution s1 -> (-s1+q) is indicated.  
		 * 
		 */
		// rewrite condition 1. above as "q:= arg - s1 is independent of s1."
		if( u.get(i).getArgument().minus(Polynomial.create(s1)).isIndependentOf(s1)){ 
		    // p1:= s1-q = 2 s1 - arg.
		    Polynomial p1 = Polynomial.create(s1).times(2).minus(
			u.get(i).getArgument());
		    
		    // s1 -> p1
		    q = q.replace(s1, p1); // in the Polynomial p which was passed to this method by the caller,
		    r = r.replace(s1, p1);
		    j = j.replace(s1, p1);
		    d.replace(s1, p1);
		    //Sunipotent suni1 = u.suniOf(s1);
		    u.replaceINeedABetterName(s1, p1);// and in this Unipotent
		    //u.associate(suni1, s1);
		}
		// rewrite condition 2. above as q:= arg + s1 is independent of s1."  
		else if( u.get(i).getArgument().plus(Polynomial.create(s1)).isIndependentOf(s1)){ 
		    
		    // p1:= -s1+q = arg.
		    Polynomial p1 = u.get(i).getArgument();
		    
		    // s1 -> p1

		    q = q.replace(s1, p1); // in the Polynomial p which was passed to this method by the caller,
		    r = r.replace(s1, p1);
		    j = j.replace(s1, p1);
		    d.replace(s1, p1);
		    //Sunipotent suni1 = u.suniOf(s1);
		    u.replaceINeedABetterName(s1, p1);// and in this Unipotent
		    //u.associate(suni1, s1);
		    /* TODO check that this if loop performs correctly.  Specific concern:
		     * the argument of Sunipotent i will be modified as part of the change of variables.
		     * Will the value of p1 change at that time?
		     */
		    
		}
	    
	    }// end if s1 not null
//	    else
//		System.out.println("String of " + u.get(i).toString() +"is null.");

	}// end for each term...
	
//	System.out.println("Making changes of variable.  After:"+ this.toString());

	
    }



    public String toString(OutputStyle sty, String name) {
	StringBuilder sb = new StringBuilder();
	
	switch (sty){
	case TEX:
	    sb.append("\\begin{equation}\\label{"+ name +"}\n");
	    sb.append(toString(sty));
	    sb.append("\\end{equation}");
	    break;
	default:
	    sb.append(toString(sty));
	}
	
	return sb.toString();
    }



    public SectionIntegral times(Sunipotent suni) {
	Unipotent u1 = Unipotent.times(getU(), suni);
	return new SectionIntegral(new Domain(d), g, w, p, u1, q, r, j);
    }



    public void lReduce() throws IOException {
	u = edu.siu.math.egut.egut.Algorithms.l_reduce(u, Algorithms.preimage(w, p.getRoots()));
	
    }



    public SectionIntegral replace(String variable, Polynomial q2) throws BadSubstitutionException, UncomputableJacobianException {
	/* FIXME
	 * A serious problem with this method is the following:
	 * of q.replace throws an exception (for example), then d and u are 
	 * altered, while the rest are not.  In other words, exceptions
	 * create "partial" substitutions.  
	 */
	d.replace(variable, q2);
	u.replace(variable, q2);
	u.clearZeros();
	q = q.replace(variable, q2);
	r = r.replace(variable, q2);
	j = j.replace(variable, q2).times(jacobian(variable, q2));
	return this;	
	
    }

    


    private static Polynomial jacobian(String x, Polynomial q2) throws UncomputableJacobianException {
	/* Desire is to produce the correct change of variable 
	 * for the substitution x -> q2.  
	 */
	if(q2.isIndependentOf(x))
	    return Polynomial.create(1);
	/*
	 * Such a case will not arise from any of the automated change of variables
	 * algorithms which are probably the primary concern.  
	 * 
	 *  Further, experience indicates that a substitution of this type is most often
	 *  made in circumstances where the Jacobian, if computable, is not relevant. 
	 *  E.g., setting up an integral with a complicated unipotent argument by starting
	 *  from a simpler one, or passage from a general case to a special case, or 
	 *  certain instances of "killing" when one can "tie two roots together."  
	 *  
	 *  In all these cases, no one is making an assertion that "this integral is 
	 *  equal to that integral by a change of variables."  Therefore, one does 
	 *  not want the Jacobian.
	 *  
	 *  At present, it seems best to require that, for any substitution of this type where the 
	 *  Jacobian is wanted, the user compute it "by hand."
	 */
	if( q2.degreeIn(x) == 1)
	    return q2.coeff(x);
	if( q2.degreeIn(x)  == -1)
	    return q2.coeff(x).times(Monomial.create(x,-2));
	throw new UncomputableJacobianException(x+"->"+q2.toString(OutputStyle.SCREEN));
    }



    public boolean hasVariable(String input) {
	return getAllVariables().contains(input);
    }



    /**
     * @param poly
     */
    public void setQ(Polynomial poly) {
	q = poly;
	
    }



    public static long getSerialversionuid() {
        return serialVersionUID;
    }



    public static String getDivider() {
        return DIVIDER;
    }



    public void setR(Polynomial r) {
        this.r = r;
    }



    public void setJ(Polynomial j) {
        this.j = j;
    }



    public void actBy(WeylWord w1) throws WeylWordReadException, IOException, BadSubstitutionException, UncomputableJacobianException, NotARootException {
	// compute new w,
	
	w = WeylWord.reduceByInverse(w,w1, g);
	
	// compute new u,
	u.actBy(w1.getWord());
	
	// make necessary changes of variable,
	/*This must be done in two stages.  If a is a root we need to change
	 * u_a to +/-u_{w.a}. However, u_{w.a} may occur elsewhere in the integral.
	 * Therefore, we first change u_a to placeholder(u, w.a), and 
	 * then go back and change placeholder(u,w.a) to u_{w.a}
	 * 
	 * (The sign of +/-u_{w.a} is determined by the structure 
	 * constants and is such that w x_a(u_a) w^(-1) will become 
	 * x_{w.a}(u_{w.a}).  Note that we make such a change of variable
	 * in EVERY default variable which is found, regardless of whether 
	 * or not x_a(u_a) is still present. There are two reasons for this:  
	 * first, only updating those subscripts that we "need to" would
	 * lead to a mixture of updated an unupdated subscripts, and likely
	 * cause confusion, if not collisions.  Second, it will facilitate 
	 * notation which is uniform across various cases in a case-by-case 
	 * analysis. )
	 *
	 */
//	u.listAssociations();
	for(String x : this.getAllVariables()){
	    PrefixRootPair prp = Unipotent.prefixRootPair(x);
	    if( prp != null && g.isRoot(prp.getRoot())){
		System.out.println(x+"is a default variable." +
				"\nSo we do "+ x +"->"+
				Polynomial.create(
					placeHolder(prp.getPrefix(), g.weylAction(w1.getWord(),  prp.getRoot()) )
				).times(g.weylSign(w1.getWord(), prp.getRoot()))
				);

		changeDefaultToPlaceHolder(x, Polynomial.create(
			placeHolder(prp.getPrefix(), g.weylAction(w1.getWord(),  prp.getRoot()) )
			/*x is s default variable, formed from some String prefix and some 
			 * root, which are recoverable.  extract them.  act by w on 
			 * the root to get a new root.  form the new default variable
			 * from the placeholder for that root and the variable.
			 * 
			 */
			 
			
				).times(g.weylSign(w1.getWord(), prp.getRoot()))// multiply by the sign corresponding to action by w on that root.
				);
	    }
	    else{
		System.out.println(x+" is not a default variable.");
	    }
	}
//	System.out.println("before removing placeholders.");
//	u.listAssociations();
	removePlaceHoldersVerbose();
//	System.out.println("after removing placeholders.");
//	u.listAssociations();
	
    }

/*TODO:  what this method is doing is essentially a text search/replace.
 *This can probably be done more efficiently than by using a polynomial
 *substitution algorithm.
 * (No longer sure whether the above comment refers to the 
 * the method above, the one below, neither, or both.)
 */


    private void changeDefaultToPlaceHolder(String variable, Polynomial q2) throws BadSubstitutionException, UncomputableJacobianException {
	d.replace(variable, q2);
	u.replaceAndShiftAssociations(variable, q2);
	u.clearZeros();
	q = q.replace(variable, q2);
	r = r.replace(variable, q2);
	j = j.replace(variable, q2).times(jacobian(variable, q2));
	
    }

   private boolean isADefaultVariable(String x) {
	
	return Unipotent.DEFAULT_VARIABLE_PATTERN.matcher(x).matches();
    }
 

    
    private Set<String> getAllVariables() {
	Set<String>  allVars = d.getAllVariables();
	allVars.addAll(u.getAllVariables());
	allVars.addAll(j.getAllVariables());
	allVars.addAll(q.getAllVariables());
	allVars.addAll(r.getAllVariables());
	
	return allVars;
    }



    private void removePlaceHolders()  {
	Set<String> gav= getAllVariables();
	
	String[] allVars= new String[gav.size()];
	allVars = gav.toArray(allVars);
	for(String x:allVars)
	    if(x.endsWith(PLACE_HOLDER_STRING) ){
		replaceWithSetArgs(x, 
			x.substring(0,
				x.length() 
				-PLACE_HOLDER_STRING.length()));
	    }
    }
   
    private void removePlaceHoldersVerbose()  {
	Set<String> gav= getAllVariables();
	
	String[] allVars= new String[gav.size()];
	allVars = gav.toArray(allVars);
	for(String x:allVars)
	    if(x.endsWith(PLACE_HOLDER_STRING) ){
		System.out.println(x + " is a place holder.  Replace with "+x.substring(0,
			x.length() 
			-PLACE_HOLDER_STRING.length()));
		replaceWithSetArgs(x, 
			x.substring(0,
				x.length() 
				-PLACE_HOLDER_STRING.length()));
	    }
	    else{
		System.out.println(x + " is not a place holder.");
	    }
    }
   



    private boolean isAPlaceHolder(Polynomial arg) {
	
	return arg.isAVariable() && arg.getVar().endsWith(PLACE_HOLDER_STRING) ;
    }



    public void replaceWithSetArgs(String var, String string)  {
	u.replace(var,string);
	d.replace(var, string);
	j = j.replace(var, string);
	q = q.replace(var, string);
	r = r.replace(var, string);
	
    }



    private String placeHolder(String prefix, int[] root) {
	
	return Unipotent.defaultString(prefix, root)+PLACE_HOLDER_STRING;
    }



    private String defaultVariablePrefix(String var) {
	return var.substring(0,var.lastIndexOf('_'));
    }



    public void addNewSunis(ArrayList<Sunipotent> newSunipotents) {
	u.addRight(newSunipotents);
	d.addVariables(newSunipotents);
	
    }



    public void replace(String var, String string) {
	d.replace(var, string);
	q = q.replace(var, string);
	r=r.replace(var, string);
	j=j.replace(var, string);
	u.replace(var, string);
	
    }



    public void checkEnd() {
	if( canShortenU()){
	    u.remove(u.getNumberOfTerms()-1);
	    simplify();	    
	    checkEnd();
	}
	
    }



    private void simplify() {
	Set<String> allVarsSet = getAllVariables();
	String[] allVars = allVarsSet.toArray(new String[allVarsSet.size()]);
	for (int i=0; i< allVars.length;  ) {
	    String x = allVars[i];
	    i++;
	    if (d.hasBound(1, Symbol.GREATERTHANOREQUALTO, x)
		    || d.hasBound(1, Symbol.GREATERTHANOREQUALTO, Polynomial
			    .create(x).times(-1))
		    || d.hasBound(-1, Symbol.GREATERTHANOREQUALTO, x)
		    || d.hasBound(-1, Symbol.GREATERTHANOREQUALTO, Polynomial
			    .create(x).times(-1))) {
		if (d.hasNoNonsimpleBoundsOn(x)) {
		    if (q.isIndependentOf(x) && r.isIndependentOf(x)
			    && j.isIndependentOf(x) && u.isIndependentOf(x)) {
			
			d.removeBound(1, Symbol.GREATERTHANOREQUALTO, x);
			d.removeBound(-1, Symbol.GREATERTHANOREQUALTO,
				Polynomial.create(x).times(-1));
			d.removeBound(1, Symbol.GREATERTHANOREQUALTO,
				Polynomial.create(x).times(-1));
			d.removeBound(-1, Symbol.GREATERTHANOREQUALTO, x);
			d.removeVariable(x);
			/*the counter i was incremented above.  if we remove 
			 * a variable, then this step is not correct, and 
			 * so we undo it.
			 */
			i--;
		    }
		}
	    }

	}
	
    }






    private boolean canShortenU() {
	if( u.getNumberOfTerms() == 0)
	    return false;
	if(integersContain(u.getArg(u.getNumberOfTerms()-1)))
	    return true;
	return false;
    }



    private boolean integersContain(Polynomial arg) {
	if( arg.equals(Polynomial.create(0))
		|| arg.equals(Polynomial.create(1))
		|| arg.equals(Polynomial.create(-1)))
	    return true;
	
	ArrayList<String> inO = new ArrayList<String>();
	ArrayList<String> notInO = new ArrayList<String>();
	for( Inequality i : d.getBounds()){
	    if( i.implies(new Inequality(Polynomial.create(1), Symbol.GREATERTHANOREQUALTO, arg)))
		return true;
	    if( i.getLHS().isConstant() && i.getRHS().isASignedVariable())
		inO.add(i.getRHS().getVar());
	    if( (i.getRHS().equals( Polynomial.create(1)) || 
		    i.getRHS().equals( Polynomial.create(-1))) 
		    && 
		    i.getLHS().isASignedVariable()
		    &&
		    i.getSymbol() == Symbol.GREATERTHAN )
		notInO.add(i.getLHS().getVar());
		
	}
	for( Entry<Monomial, Integer> e: arg.getTerms())
	    for (String x : e.getKey().getVariables()) {
		if (inO.contains(x) && e.getKey().degreeIn(x) < 0)
		    return false;
		if (notInO.contains(x) && e.getKey().degreeIn(x) > 0)
		    return false;
		if ((!inO.contains(x)) && (!notInO.contains(x)))
		    return false;
	    }
	return true;
    }



    public ArrayList<int[]> getPsiRoots() {
	ArrayList<int[]> answer = new ArrayList<int[]>();
	for( String x: q.getAllVariables())
	    if( u.suniOf(x) != null)
		answer.add(u.suniOf(x).getRoot());
	return answer;
    }



    public void clearZeros() {
	u.clearZeros();
	
    }



   


 

 
	
	
    






    
    
 
    
    
}
